package com.manlitech.cloudboot.gateway.config;
/*
 *Created by Intellij IDEA.
 *User:  Shensg
 *Date:  2021/2/2
 */


import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.common.utils.StringUtils;
import com.manlitech.cloudboot.common.constant.LogAndAuthConstant;
import com.manlitech.cloudboot.common.utils.Result;
import com.manlitech.cloudboot.common.utils.ResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.*;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Component
@Slf4j
public class AuthAndLogFilter implements GlobalFilter, Ordered {

    @Autowired
    private MyGatewaySelfConfig myGatewaySelfConfig;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        StringBuilder logBuilder = new StringBuilder();

        String gloableTraceId = getGloableTraceId(serverHttpRequest);
        MDC.put(LogAndAuthConstant.TRACEID , gloableTraceId);

        /** 验签*/
        Map<String, String> params = parseRequest(exchange, logBuilder);
        boolean r = checkSignature(params, serverHttpRequest);
        if(!r) {
            return returnErrorMsgForJson(logBuilder , serverHttpResponse , ResultUtil.error(-5,"token验证失败"));
        }

        /** 请求响应的日志*/
        DataBufferFactory bufferFactory = serverHttpResponse.bufferFactory();
        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(serverHttpResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(fluxBody.map(dataBuffer -> {
                        byte[] content = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(content);
                        DataBufferUtils.release(dataBuffer);
                        String resp = new String(content, Charset.forName("UTF-8"));
                        logBuilder.append(",traceId=").append(gloableTraceId);
                        logBuilder.append(",resp=").append(resp);
                        log.info(logBuilder.toString());
                        byte[] uppedContent = new String(content, Charset.forName("UTF-8")).getBytes();
                        return bufferFactory.wrap(uppedContent);
                    }));
                }
                return super.writeWith(body);
            }
        };

        /** 添加traceId作为链路请求唯一*/
        if(exchange.getRequest().getHeaders().get(LogAndAuthConstant.TRACEID) == null){
            Consumer<HttpHeaders> httpHeaders = httpHeader -> httpHeader.set(LogAndAuthConstant.TRACEID, gloableTraceId);
            ServerHttpRequest host = exchange.getRequest().mutate().headers(httpHeaders).build();
            return chain.filter(exchange.mutate().request(host).response(decoratedResponse).build());
        }
        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }

    /**
     * 获取全局的traceId
     * @param serverHttpRequest
     * @return
     */
    private String getGloableTraceId(ServerHttpRequest serverHttpRequest) {
        List<String> traceIdList = serverHttpRequest.getHeaders().get(LogAndAuthConstant.TRACEID);
        if(traceIdList == null){
            return UUID.randomUUID().toString() ;
        }else{
            String traceId = traceIdList.get(0);
            if(StringUtils.isNotBlank(traceId)) {
                return traceId;
            }else{
                return UUID.randomUUID().toString() ;
            }
        }
    }



    /**
     * 签名校验
     * @param params
     * @param serverHttpRequest
     * @return
     */
    private boolean checkSignature(Map<String, String> params, ServerHttpRequest serverHttpRequest) {
        //获取项目名
        String requstPath = getRequestBaseContext(serverHttpRequest.getPath().pathWithinApplication().value());
        String ignorecheckbasepath = myGatewaySelfConfig.getIgnorecheckbasepath();
        /**
         * 验签过滤地址 例如授权 oauth-server
         */
        if(ignorecheckbasepath!=null && !ignorecheckbasepath.isEmpty()
            && requstPath!=null && !requstPath.isEmpty()
            && requstPath.indexOf(ignorecheckbasepath)>-1){
            log.info("请求非拦截token服务");
            return true;
        }
        List<String> tokenList = serverHttpRequest.getHeaders().get(LogAndAuthConstant.TOKEN);
        if(tokenList == null){
            return false;
        }
        String token = tokenList.get(0);
        if(StringUtils.isBlank(token)) {
            return false;
        }
        return true;
    }

    public String getRequestBaseContext(String msg){
        // 按指定模式在字符串查找
        String pattern = "(?<=\\/)[^/]+(?=\\/)";
        // 创建 Pattern 对象
        Pattern r = Pattern.compile(pattern);
        // 现在创建 matcher 对象
        Matcher m = r.matcher(msg);
        if (m.find( )) {
            return m.group(0) ;
        } else {
            return "";
        }
    }



    /**
     * 获取参数
     * @param exchange
     * @param logBuilder
     * @return
     */
    private Map<String, String> parseRequest(ServerWebExchange exchange, StringBuilder logBuilder) {
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        String method = serverHttpRequest.getMethodValue().toUpperCase();
        logBuilder.append(method).append(",").append(serverHttpRequest.getURI());
        MultiValueMap<String, String> query = serverHttpRequest.getQueryParams();
        Map<String, String> params = new HashMap<>();
        query.forEach((k, v) -> {
            params.put(k, v.get(0));
        });
        if("POST".equals(method)) {
            String body = exchange.getAttributeOrDefault("cachedRequestBody", "");
            if(StringUtils.isNotBlank(body)) {
                logBuilder.append(",body=").append(body);
                String[] kvArray = body.split("&");
                for (String kv : kvArray) {
                    if (kv.indexOf("=") >= 0) {
                        String k = kv.split("=")[0];
                        String v = kv.split("=")[1];
                        if(!params.containsKey(k)) {
                            try {
                                params.put(k, URLDecoder.decode(v, "UTF-8"));
                            } catch (UnsupportedEncodingException e) {
                            }
                        }
                    }
                }
            }
        }
        return params;
    }

    public Mono<Void> returnErrorMsgForJson (StringBuilder logBuilder , ServerHttpResponse serverHttpResponse , Result result){
        String resp = JSON.toJSONString(result);
        logBuilder.append(",resp=").append(resp);
        log.info(logBuilder.toString());
        DataBuffer bodyDataBuffer = serverHttpResponse.bufferFactory().wrap(resp.getBytes());
        serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return serverHttpResponse.writeWith(Mono.just(bodyDataBuffer));
    }

    @Override
    public int getOrder() {
        return -20;
    }
}

